﻿using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Rapidity.Http.Attributes;
using Rapidity.Http.Extensions;
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Rapidity.Http.DynamicProxies
{
    /// <summary>
    /// https://stackoverflow.com/questions/37526165/compiling-and-running-code-at-runtime-in-net-core-1-0
    /// https://benohead.com/three-options-to-dynamically-execute-csharp-code/
    /// </summary>
    internal static class ProxyGenerator
    {
        /// <summary>
        /// 接口动态生成Assembly
        /// </summary>
        /// <param name="types"></param>
        /// <returns></returns>
        internal static Assembly Generate(Type[] types, CodeGeneratorOption option = default)
        {
            var template = BuildTemplate(types);
            var source = GenerateCode(template);
            if (option.SaveCode)
            {
                if (string.IsNullOrEmpty(option.BaseDirectory))
                    option.BaseDirectory = AppDomain.CurrentDomain.BaseDirectory;
                if (!Directory.Exists(option.BaseDirectory))
                    Directory.CreateDirectory(option.BaseDirectory);
                var path = Path.Combine(option.BaseDirectory, "GeneratedCode.cs");
                File.WriteAllText(path, source, Encoding.UTF8);
            }
            //获取dotnetCore运行环境目录
            var dotnetCoreDirectory = System.Runtime.InteropServices.RuntimeEnvironment.GetRuntimeDirectory();
            var compilation = CSharpCompilation.Create("DynamicGenerated")
                .WithOptions(new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary))
                //基础类库引用
                .AddReferences(MetadataReference.CreateFromFile(Path.Combine(dotnetCoreDirectory, "netstandard.dll")))
                .AddReferences(MetadataReference.CreateFromFile(Path.Combine(dotnetCoreDirectory, "System.Runtime.dll")))
                .AddReferences(MetadataReference.CreateFromFile(typeof(object).GetTypeInfo().Assembly.Location))
                //dynamic类型需要引用 System.Linq.Expressions.dll
                .AddReferences(MetadataReference.CreateFromFile(typeof(System.Linq.Expressions.Expression).GetTypeInfo().Assembly.Location))
                .AddReferences(MetadataReference.CreateFromFile(typeof(ProxyGenerator).GetTypeInfo().Assembly.Location))
                //添加所有types的引用
                .AddReferences(types.Select(x => MetadataReference.CreateFromFile(x.GetTypeInfo().Assembly.Location)))
                //加载编译代码
                .AddSyntaxTrees(CSharpSyntaxTree.ParseText(source));

            using (var ms = new MemoryStream())
            {
                var result = compilation.Emit(ms);
                if (!result.Success)
                {
                    StringBuilder sb = new StringBuilder();
                    foreach (var diag in result.Diagnostics)
                    {
                        if (sb.Length > 0) sb.AppendLine();
                        sb.Append(diag.GetMessage());
                    }
                    var errorMsg = sb.ToString();
                    Trace.TraceError(errorMsg);
                    throw new Exception(errorMsg);
                }
                return Assembly.Load(ms.GetBuffer());
            }
        }

        /// <summary>
        /// 模板数据生成
        /// </summary>
        /// <param name="types"></param>
        /// <returns></returns>
        internal static TemplateMetadata BuildTemplate(Type[] types)
        {
            var template = new TemplateMetadata();

            template.UsingList.Add(typeof(object).Namespace);
            template.UsingList.Add(typeof(Thread).Namespace);
            template.UsingList.Add(typeof(Task).Namespace);
            template.UsingList.Add(typeof(IHttpService).Namespace);
            template.UsingList.Add(typeof(HttpServiceAttribute).Namespace);
            template.UsingList.Add(typeof(HttpClientWrapperExtension).Namespace);

            foreach (var type in types)
            {
                var classTemplate = new ClassTemplate()
                {
                    InterfaceType = type,
                    Namespace = $"Dynamic.{RandomName()}",
                    Name = $"AutoGenerated{type.Name.TrimStart('I').Replace("`", string.Empty)}"
                };
                classTemplate.UsingList.Add(type.Namespace);
                //类标签
                foreach (var attr in CustomAttributeData.GetCustomAttributes(type))
                    classTemplate.AttributeList.Add(new CustomAttributeTemplate(attr).ToString());
                //泛型类信息收集
                if (type.IsGenericType)
                {
                    foreach (var argumentType in type.GetGenericArguments())
                    {
                        var argument = new GenericArgumentTemplate()
                        {
                            Name = argumentType.Name
                        };
                        classTemplate.GenericArguments.Add(argument);

                        if (argumentType.GenericParameterAttributes == GenericParameterAttributes.None
                            && argumentType.GetGenericParameterConstraints().Length == 0) continue;

                        //class约束
                        if (argumentType.GenericParameterAttributes.HasFlag(GenericParameterAttributes.ReferenceTypeConstraint))
                            argument.Constraints.Add("class");
                        //值类型约束
                        if (argumentType.GenericParameterAttributes.HasFlag(GenericParameterAttributes.NotNullableValueTypeConstraint))
                            argument.Constraints.Add("struct");

                        //类型约束
                        foreach (var constraintType in argumentType.GetGenericParameterConstraints())
                            argument.Constraints.Add(new TypeTemplate(constraintType).ToString());

                        //无参构造函数
                        if (argumentType.GenericParameterAttributes.HasFlag(GenericParameterAttributes.DefaultConstructorConstraint))
                            argument.Constraints.Add("new()");
                    }
                }

                //方法信息
                foreach (var method in type.GetRuntimeMethods())
                {
                    if (method.IsSpecialName) continue;
                    classTemplate.MethodList.Add(new MethodTemplate(method));
                }
                //属性
                foreach (var property in type.GetRuntimeProperties())
                {
                    if (property.IsSpecialName) continue;
                    classTemplate.PropertyList.Add(new PropertyTemplate(property));
                }
                //todo:不支持事件
                template.ClassList.Add(classTemplate);
            }
            return template;
        }

        /// <summary>
        /// 组装代码
        /// </summary>
        /// <param name="template"></param>
        /// <returns></returns>
        internal static string GenerateCode(TemplateMetadata template)
        {
            StringBuilder builder = new StringBuilder(2048);

            foreach (var @using in template.UsingList)
                builder.AppendFormat("using {0};", @using).AppendLine();

            builder.AppendLine();
            foreach (var @class in template.ClassList)
            {
                int indentLevel = 0; //缩进级别
                builder.Append($"namespace ").Append(@class.Namespace).AppendLine();
                builder.Append("{").AppendLine();
                indentLevel++;
                foreach (var classUsing in @class.UsingList)
                {
                    builder.AppendIndent(indentLevel).AppendFormat("using {0};", classUsing).AppendLine();
                }
                builder.AppendLine();
                //创建类
                builder.AppendIndent(indentLevel).Append("/*").AppendLine();
                builder.AppendIndent(indentLevel).Append(" *").AppendFormat(" {0} implement {1}", @class.Name, new TypeTemplate(@class.InterfaceType).Name).AppendLine();
                builder.AppendIndent(indentLevel).Append(" */").AppendLine();
                foreach (var classAttr in @class.AttributeList)
                    builder.AppendIndent(indentLevel).Append(classAttr).AppendLine();
                builder.AppendIndent(indentLevel).Append("public class ").Append(@class.Name);
                var genericArg = string.Join(",", @class.GenericArguments.Select(x => x.Name));
                if (!string.IsNullOrEmpty(genericArg)) builder.AppendFormat("<{0}>", genericArg);
                //接口实现
                builder.Append(" : ");
                builder.Append(new TypeTemplate(@class.InterfaceType).Name);
                if (!string.IsNullOrEmpty(genericArg)) builder.AppendFormat("<{0}>", genericArg);
                //泛型约束
                foreach (var argument in @class.GenericArguments)
                {
                    builder.AppendFormat(" where {0} : {1}", argument.Name, string.Join(",", argument.Constraints));
                }
                builder.AppendLine();
                builder.AppendIndent(indentLevel).Append('{').AppendLine();
                indentLevel++;

                //私有字段
                builder.AppendIndent(indentLevel).Append("private readonly IRequestDescriptorBuilder _builder;").AppendLine();
                builder.AppendIndent(indentLevel).Append("private readonly IHttpClientWrapper _client;").AppendLine();
                builder.AppendLine();
                //---构造函数-----
                builder.AppendIndent(indentLevel).Append("public ").Append(@class.Name).Append("(IRequestDescriptorBuilder builder, IHttpClientWrapper client)")
                    .AppendLine();
                builder.AppendIndent(indentLevel).Append('{').AppendLine();

                indentLevel++;
                builder.AppendIndent(indentLevel).Append("_builder = builder;").AppendLine();
                builder.AppendIndent(indentLevel).Append("_client = client;").AppendLine();
                indentLevel--;
                builder.AppendIndent(indentLevel).Append('}').AppendLine();
                //--构造函数结束-----
                builder.AppendLine();

                //---属性开始---
                foreach (var property in @class.PropertyList)
                {
                    builder.AppendLine();
                    builder.AppendIndent(indentLevel).Append(property.ToString()).AppendLine();
                }
                //---属性结束---

                //----方法开始----
                foreach (var method in @class.MethodList)
                {
                    builder.AppendLine();
                    builder.AppendIndent(indentLevel).Append("// ").Append(method.Name).AppendLine();
                    //方法标签
                    foreach (var methodAttr in method.AttributeList)
                        builder.AppendIndent(indentLevel).Append(methodAttr).AppendLine();

                    //方法签名
                    builder.AppendIndent(indentLevel).Append("public ");
                    if (method.IsAsync) builder.Append("async ");
                    builder.Append(method.ReturnType.ToString()).Append(" ");
                    builder.Append(method.Name);
                    if (method.GenericArguments.Count > 0)
                    {
                        builder.AppendFormat("<{0}>", string.Join(",", method.GenericArguments.Select(x => x.Name)));
                    }
                    builder.Append("(");
                    builder.Append(string.Join(",", method.Parameters));
                    builder.Append(")");
                    //方法泛型约束
                    foreach (var argument in method.GenericArguments)
                    {
                        builder.AppendFormat(" where {0} : {1}", argument.Name, string.Join(",", argument.Constraints));
                    }
                    builder.AppendLine();
                    //---方法签名结束
                    builder.AppendIndent(indentLevel).Append("{").AppendLine();
                    indentLevel++;
                    //---方法体开始-----
                    //var request = _builder.Build(typeof(ITokenService), token, template);
                    builder.AppendIndent(indentLevel).AppendFormat("var request = _builder.Build(this.GetType(),{0});", string.Join(",", method.Parameters.Select(x => x.Name)));
                    builder.AppendLine();

                    //获取token变量
                    var tokenParam = method.Method.GetParameters().LastOrDefault(x => x.ParameterType == typeof(CancellationToken));
                    var tokenName = tokenParam != null
                              ? method.Parameters.ToArray()[tokenParam.Position].Name
                              : "CancellationToken.None";

                    builder.AppendIndent(indentLevel).Append(ClientInvokeCode(method.ReturnType.Type, tokenName)).AppendLine();
                    //---方法体结束-----
                    indentLevel--;
                    builder.AppendIndent(indentLevel).Append("}").AppendLine();
                }
                //--方法结束--
                indentLevel--;
                builder.AppendIndent(indentLevel).Append('}').AppendLine();
                //--类结束--
                indentLevel--;
                builder.AppendIndent(indentLevel).Append("}").AppendLine().AppendLine();
                //--命名空间结束--
            }
            return builder.ToString();
        }

        /// <summary>
        /// clientWarpper调用代码生成
        /// </summary>
        /// <param name="returnType"></param>
        /// <param name="tokenName"></param>
        /// <returns></returns>
        private static string ClientInvokeCode(Type returnType, string tokenName)
        {
            var isAsync = returnType.FullName.StartsWith(typeof(Task).FullName);
            if (isAsync)
            {
                //返回Task
                if (returnType == typeof(Task))
                    return $"await _client.SendAsync(request,{tokenName});";
                //返回Task<ResponseWrapper>
                if (returnType == typeof(Task<ResponseWrapper>))
                    return $"return await _client.SendAndWrapAsync(request,{tokenName});";
                if (returnType.IsGenericType)
                {
                    if (returnType.Namespace == typeof(Task<>).Namespace)
                    {
                        var genericArgument = returnType.GenericTypeArguments[0];
                        if (genericArgument.IsGenericType)
                        {
                            var argument = genericArgument.GenericTypeArguments[0];
                            //返回Task<ResponseWrapper<T>>
                            if (genericArgument.Namespace == typeof(ResponseWrapper<>).Namespace)
                            {
                                var argumentTemplate = new TypeTemplate(argument);
                                return $"return await _client.SendAndWrapAsync<{argumentTemplate}>(request,{tokenName});";
                            }
                            //返回Task<Generic<T>> task内部还是一个泛型类
                            return $"return await _client.SendAsync<{new TypeTemplate(argument)}>(request,{tokenName});";
                        }
                        //返回Task<T>
                        var template = new TypeTemplate(genericArgument);
                        return $"return await _client.SendAsync<{template}>(request,{tokenName});";
                    }
                }
            }
            //返回void
            if (returnType == typeof(void))
                return $"_client.SendAndWrapAsync(request,{tokenName}).GetAwaiter().GetResult();";
            //返回ResponseWrapper
            if (returnType == typeof(ResponseWrapper))
            {
                return $"return _client.SendAndWrapAsync(request,{tokenName}).GetAwaiter().GetResult();";
            }
            //返回ResponseWrapper<T>
            if (returnType.IsGenericType && returnType.Namespace == typeof(ResponseWrapper<>).Namespace)
            {
                var genericArgument = returnType.GenericTypeArguments[0];
                var template = new TypeTemplate(genericArgument);
                return $"return _client.SendAndWrapAsync<{template}>(request,{tokenName}).GetAwaiter().GetResult();";
            }
            //返回 data
            return $"return _client.SendAsync<{new TypeTemplate(returnType)}>(request,{tokenName}).GetAwaiter().GetResult();";
        }

        /// <summary>
        /// 随机名称
        /// </summary>
        /// <param name="length"></param>
        /// <returns></returns>
        private static string RandomName(int length = 16)
        {
            var source = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
            var random = new Random(Environment.TickCount);
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < length; i++)
            {
                var num = random.Next(0, source.Length - 1);
                sb.Append(source[num]);
            }
            return sb.ToString();
        }
    }

    internal static class StringBuilderExtension
    {
        /// <summary>
        /// 添加缩进
        /// </summary>
        /// <param name="builder"></param>
        /// <param name="level"></param>
        /// <returns></returns>
        public static StringBuilder AppendIndent(this StringBuilder builder, int level)
        {
            if (level <= 0) return builder;
            for (int i = 0; i < level; i++)
                builder.Append('\t');
            return builder;
        }
    }

    /// <summary>
    /// 
    /// </summary>
    public struct CodeGeneratorOption
    {
        public bool SaveCode { get; set; }

        public string BaseDirectory { get; set; }

        public static CodeGeneratorOption Default
        {
            get
            {
                return new CodeGeneratorOption
                {
                    BaseDirectory = AppDomain.CurrentDomain.BaseDirectory,
#if DEBUG
                    SaveCode = true
#endif
                };
            }
        }
    }
}